---
title: ChangeUserAccount
summary: Updates optional profile and directory attributes on an external-domain account, gated by the IP allow-list.
status: draft
owner: "Not determined from source"
last_reviewed: 2026-09-22
---

# ChangeUserAccount (OP-09)

## Request and response

| Part | Type | Description |
| --- | --- | --- |
| `inUserId` | `String` | The `SamAccountName` of the account to update. |
| `inGivenName` | `String` | Written to `GivenName` only when non-empty, setting `modified = True`. |
| `inSurName` | `String` | Written to `Surname` only when non-empty, setting `modified = True`. |
| `inDescription` | `String` | Written to `Description` only when non-empty, setting `modified = True`. |
| `inPhoneNum` | `String` | Written to `telephoneNumber` only when non-empty, setting `modified = True`. |
| `inMailAddr` | `String` | Written to `mail` only when non-empty, setting `modified = True`. |
| `inTitle` | `String` | Written to `title` only when non-empty, setting `modified = True`. |
| `inInfo` | `String` | Written to `info` only when non-empty; does **not** set `modified`. |
| `inCompany` | `String` | Written to `company` only when non-empty; does **not** set `modified`. |
| `inFAXNum` | `String` | Written to `facsimileTelephoneNumber` only when non-empty, setting `modified = True`. |

| Part | Type | Description |
| --- | --- | --- |
| `<return>` | `ADServiceResponse` | `Response` is `Success`, `UserDoesNotExist`, or `Failure`. `Message` carries the caught exception text on a `Failure`, otherwise empty. |

## WebMethod attributes

`<WebMethod()>` — bare, no arguments supplied.

## Steps

1. `CheckAccess()`; on denial return `AccessDenied()` ([BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller)).
2. Call H1 `GetContext` for the external domain.
3. Initialize the response sentinel to `Success`; set local `modified = False`.
4. Open `Try`.
5. `UserPrincipal.FindByIdentity(prinContext, inUserId)` **directly** — this operation does not use H2, so a `Nothing` context would fault here rather than being swallowed*.
   - If `Nothing`, set `Response = UserDoesNotExist`.
6. Else set `GivenName`, `Surname`, and `Description` only when the matching input is non-empty, setting `modified = True` for each write ([BR-23](../business-rules.md#br-23-an-account-change-updates-only-non-empty-fields)).
7. If `modified`, call `userPrin.Save()`.
8. Reset `modified = False`; `de = userPrin.GetUnderlyingObject`.
9. Write `company` (**without** setting `modified` — tech debt TD-19), `telephoneNumber`, `mail`, `title`, `info` (**without** setting `modified` — tech debt TD-19), and `facsimileTelephoneNumber`, each only when the matching input is non-empty ([BR-13](../business-rules.md#br-13-an-optional-attribute-is-written-only-when-supplied)).
10. If `modified`, call `de.CommitChanges()` ([BR-24](../business-rules.md#br-24-directory-attribute-changes-commit-only-when-modified-is-raised)) — a request that changes **only** `inCompany` and/or `inInfo` returns `Success` having committed nothing (tech debt TD-19, HIGH — see [Tech debt](../tech-debt.md)).
11. `Catch ex As Exception` → set `Response = Failure`, `Message = ex.Message`.
12. `Finally` releases `de`, `prinContext`, and `userPrin`.
13. Return the response sentinel.

## Process flow

```mermaid
flowchart TD
    A["Call ChangeUserAccount"] --> B{"CheckAccess<br>HasAccess?"}
    B -->|"No"| C["Return AccessDenied"]
    B -->|"Yes"| D["FindByIdentity direct<br>no H2 wrapper"]
    D -->|"Nothing"| E["Set UserDoesNotExist"]
    D -->|"Found"| F["Set GivenName/Surname<br>Description if non-empty"]
    F --> G{"modified?"}
    G -->|"Yes"| H["userPrin.Save"]
    G -->|"No"| I["Skip Save"]
    H --> J["Reset modified = False"]
    I --> J
    J --> K["Write company/info<br>without setting modified"]
    K --> L["Write phone/mail/title/fax<br>setting modified"]
    L --> M{"modified?"}
    M -->|"Yes"| N["de.CommitChanges"]
    M -->|"No"| O["Skip CommitChanges<br>company/info silently lost"]
    N --> P["Return response"]
    O --> P
    E --> P
    D -.->|"Exception"| Q["Set Failure + ex.Message"]
    Q --> P
```

## Shared state effects

None — this operation touches no process-wide state; all locals are per-request.

## Error handling

The operation returns a sentinel `ADServiceResponse` for every outcome inside the `Try` block. It never surfaces a framework SOAP fault from a caught failure, but because `FindByIdentity` is called directly rather than through H2, a `Nothing` context (rather than a resolvable-vs-unresolvable user) would still be caught by the same `Try`/`Catch` and reported as `Failure`*. See [Faults](../faults.md) for the service-wide fault model.

## Authorization

Gated by [BR-01](../business-rules.md#br-01-mutating-and-password-operations-require-an-approved-caller): the caller must pass the IP allow-list check enforced by `CheckAccess`. See [Authentication and authorization](../authentication-authorization.md).

> **Note:** Statements marked with * were inferred from code structure, naming conventions, or
> contextual evidence where direct confirmation was not available in source artifacts. Statements
> marked with ** could not be confirmed; treat as unknown until verified.

## Related documentation
- [Service index](../index.md)
- [Overview](../overview.md)
- [Architecture](../architecture.md)
- [Activation and hosting](../activation-hosting.md)
- [Operations](../operations.md)
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [Authentication and authorization](../authentication-authorization.md)
- [Configuration](../configuration.md)
- [Dependencies](../dependencies.md)
- [Faults](../faults.md)
- [Operational runbook](../operational-runbook.md)
- [Tech debt](../tech-debt.md)

## Sources
- `src\ADUserServices\clsADUserServices.asmx.vb` (lines 388-469)
